package at.fhj.itm11.antesk.memory.model;


import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.Timer;

import at.fhj.itm11.antesk.memory.view.StatusBar;

/**
 * This class contains the logical operations for the game to work.
 * The singleton pattern is used to ensure the same variables
 * globally.
 * 
 * @author Ivan Antes-Klobucar
 * @version 1.0
 *
 */
public class Logic{
	
	private Field[] fields;
	private int pool;
	private Random rand;
	private Field[] buttons;
	private int clickCounter;
	private static Logic instance = null;
	private boolean enabled;
	private Player p;
	private int uncovered;
	private int elapsedTime;
	private ActionListener timeCounter;
	private Timer t;

	public int getElapsedTime() {
		return elapsedTime;
	}
	
	public void setElapsedTime(int elapsedTime) {
		this.elapsedTime = elapsedTime;
	}

	public int getUncovered() {
		return uncovered;
	}

	public void setUncovered(int uncovered) {
		this.uncovered = uncovered;
	}

	/**
	 * Initializes the Logic class with the given fieldSize
	 * and poolSize. Singleton pattern, hence private.
	 * 
	 * @param fieldSize Size of the array of Field objects
	 * @param poolSize Pool of random numbers to be generated
	 */
	private Logic(int fieldSize, int poolSize){
		fields = new Field[fieldSize];
		pool = poolSize;
		rand = new Random();
		buttons = new Field[2];
		clickCounter = 0;
		enabled = true;
		p = new Player();
		uncovered = 0;
		elapsedTime = 0;
		
		timeCounter();
	}
	
	/**
	 * Returns the only instance of this class. If no instance
	 * exists, one is initialized with the given values.
	 * 
	 * @return Logic object (singleton)
	 */
	public static synchronized Logic getInstance(){
		if(instance == null){
			instance = new Logic(16, 8);
		}
			
		return instance;
	}
	
	/**
	 * Returns the only instance of this class. If no instance
	 * exists, on is initialized with the given values.
	 * <p>
	 * Required for dynamic field initialization to work.
	 * 
	 * @param n Number of fields to generate
	 * @return Logic object (singleton)
	 */
	public static synchronized Logic getInstance(int n){
		if(instance == null){
			instance = new Logic(n, n/2);
		}
			
		return instance;
	}
	
	/**
	 * Deletes the single instance of this class.
	 */
	public static synchronized void destroyInstance(){
		instance = null;
	}

	public Timer getT() {
		return t;
	}

	public Player getP() {
		return p;
	}

	public boolean isEnabled() {
		return enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public int getClickCounter() {
		return clickCounter;
	}
	
	public void setClickCounter(int clickCounter) {
		this.clickCounter = clickCounter;
	}
	
	/**
	 * Returns a Field[] array with only duplicate values.
	 * 
	 * @return Field[] (JButtons)
	 */
	public Field[] getField(){
		ArrayList<Integer> used = new ArrayList<Integer>();
		int r = rand.nextInt(pool);
		
		for(int i = 0; i < fields.length; i += 2){
			while(used.contains(r))
				r = rand.nextInt(pool);
			
			fields[i] = fields[i + 1] = new Field(r);
			used.add(r);
		}
		
		return fields;
	}
	
	/**
	 * Changes the order of the items in the array by
	 * using the Fisher-Yates shuffle algorithm.
	 * 
	 * @param fields Array of type Field[]
	 */
	public Field[] shuffleFields(Field[] fields){
		Field[] f = fields;
		
		for (int i = f.length - 1; i >= 1; --i){
			int j = rand.nextInt(i + 1);
			Field temp = f[i];
			f[i] = f[j];
			f[j] = temp;
		}
		
		return f;
	}
	
	/**
	 * Checks if the selected Fields values match. Returns true
	 * if they do.
	 * <p>
	 * When the clickCounter reaches 1, the next click will be validated.
	 * If the last two buttons had the same value, the method returns
	 * true and leaves the correct fields uncovered. If they don't
	 * match, the fields stay uncovered for a set amount of milli-
	 * seconds before they are cleared again. In this case false is returned.
	 * 
	 * @return true if match
	 */
	public boolean validateEntry(Field fld){
		if(clickCounter == 0){
			buttons[0] = fld;
			clickCounter++;
			fld.setEnabled(false);
			fld.setIcon(fld.getImg());
		}
		else if(clickCounter == 1){
			buttons[1] = fld;
			
			if(buttons[0].getId() == buttons[1].getId()){
				clickCounter = 0;
				buttons[0].setEnabled(false);
				buttons[1].setEnabled(false);
				buttons[1].setIcon(buttons[1].getImg());
				
				p.updateScore( getTimedScore() );
				
				uncovered += 1;
				
				if(uncovered == fields.length / 2){
					StatusBar.scoreLabel.setText("Game Over! - Score: " + p.getScore() + " - Time: " + elapsedTime);
					t.stop();
				}
				
				return true;
			}
			
			fld.setEnabled(false);
			fld.setIcon(fld.getImg());
			
			ActionListener performer = new ActionListener() {
				
				@Override
				public void actionPerformed(ActionEvent e) {
					// when timer is finished, do
					buttons[0].setEnabled(true);
					buttons[1].setEnabled(true);
					
					buttons[0].setIcon(null);
					buttons[1].setIcon(null);
					
					clickCounter = 0;
					enabled = true;
					((Timer) e.getSource()).stop();
				}
			};
			Timer t = new Timer(500, performer);
			t.start();
			enabled = false;
			
		}
		
		return false;
	}
	
	/**
	 * Checks the elapsed time for the correct score calculation.
	 * <p>
	 * Times are subject to change.
	 * 
	 * @return int Number of points to update.
	 */
	public int getTimedScore(){
		if(elapsedTime <= 10)
			return 10;
		else if(elapsedTime > 10 && elapsedTime <= 20)
			return 5;
		else if(elapsedTime > 20 && elapsedTime <= 30)
			return 3;
		else if(elapsedTime > 30 && elapsedTime <= 60)
			return 2;
		else
			return 1;
	}
	
	/**
	 * Initializes the time counter for the game.
	 */
	private void timeCounter(){
		timeCounter = new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				elapsedTime++; // after ~68 years an exception may occur
				StatusBar.scoreLabel.setText("Score: " + p.getScore() + " - Elapsed Time: " + elapsedTime);
			}
		};
		t = new Timer(1000, timeCounter);
		t.start();
	}
	
}
